using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Perlin_Interactive_Demo_1
{
    static public class Util
    {
        static Random generator = new Random();
        public static float RandomFloat(float start, float end)
        {
            return start + ((end - start) * (float)generator.NextDouble());
        }
    }

    public static class Camera
    {
        static Matrix vp;
        static Vector3 pos = Vector3.Zero;
        static Vector2 ang = Vector2.Zero;
        static Vector3 lastForward = Vector3.Forward;
        static Vector2 lastMouse = Vector2.Zero;

        public static Matrix ViewProjection
        {
            get
            {
                return vp;
            }
        }

        public static void Initalize(Vector3 position, Vector3 direction)
        {
            pos = position;
            lastForward = direction;

            
        }

        public static void Update()
        {
            Vector3 v = Vector3.Zero;

            KeyboardState ks = Keyboard.GetState();
            MouseState ms = Mouse.GetState();

            if (ks.IsKeyDown(Keys.W) || ks.IsKeyDown(Keys.Up))
                v.Z += 0.1f;
            if (ks.IsKeyDown(Keys.S) || ks.IsKeyDown(Keys.Down))
                v.Z -= 0.1f;
            if (ks.IsKeyDown(Keys.A) || ks.IsKeyDown(Keys.Left))
                v.X -= 0.1f;
            if (ks.IsKeyDown(Keys.D) || ks.IsKeyDown(Keys.Right))
                v.X += 0.1f;

            if (ks.IsKeyDown(Keys.LeftShift) || ks.IsKeyDown(Keys.RightShift))
                v *= 5;

            Vector2 mouse = new Vector2(-ms.X, ms.Y);
            Vector2 newAng = ang - (mouse - lastMouse) * 0.01f;
            
            lastMouse = mouse;

            Vector3 right = new Vector3((float)Math.Sin((double)-newAng.X), 0f, (float)Math.Cos((double)-newAng.X));
            Vector3 newForward = Vector3.Cross(Vector3.Up, right);

            newForward = Vector3.TransformNormal(newForward, Matrix.CreateFromAxisAngle(right, newAng.Y));
            newForward.Normalize();
            ang.X = newAng.X;
            if (Math.Acos(Vector3.Dot(newForward, Vector3.Normalize(new Vector3(newForward.X, 0f, newForward.Z)))) < MathHelper.ToRadians(85.0f))
            {
                lastForward = newForward;
                ang.Y = newAng.Y;
            }

            right.Normalize();

            pos += (lastForward * v.Z) + (right * v.X);

            Matrix view = Matrix.CreateLookAt(pos, pos + lastForward, Vector3.Up);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(70f), 4f / 3f, 0.1f, 100f);

            vp = view * proj;
        }
    }

    public class Demo : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont demofont;
        
        class Cloud
        {
            Matrix world = Matrix.Identity;

            public void Create(GraphicsDevice GraphicsDevice, ContentManager Content)
            {
                const int COUNT = 100;
                demoVertices = new VertexPositionNormalTexture[COUNT];
                const float SPREAD = 1f;
                for (int i = 0; i < COUNT; i++)
                {
                    Vector3 offset = new Vector3(Util.RandomFloat(-4f * SPREAD, 4f * SPREAD), Util.RandomFloat(-1f * SPREAD, 1f * SPREAD), Util.RandomFloat(-4f * SPREAD, 4f * SPREAD));


                    demoVertices[i] = new VertexPositionNormalTexture(
                        offset,
                        new Vector3(Util.RandomFloat(0.7f, 1.0f), (offset.Y + 2) * Util.RandomFloat(600f, 900f), 0f),
                        Vector2.Zero);
                }

                demoVB = new VertexBuffer(GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * demoVertices.Length, BufferUsage.Points);
                demoVB.SetData<VertexPositionNormalTexture>(demoVertices);
                demoDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionNormalTexture.VertexElements);
                const uint CLOUD_SPREAD = 15;
                world = Matrix.CreateTranslation(Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD), Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD), Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD));
            }

            public void Draw(GraphicsDevice GraphicsDevice, Effect demoShader)
            {
                GraphicsDevice.VertexDeclaration = demoDeclaration;
                GraphicsDevice.Vertices[0].SetSource(demoVB, 0, VertexPositionNormalTexture.SizeInBytes);


                demoShader.Parameters["ViewProjection"].SetValue(Camera.ViewProjection);
                demoShader.Parameters["World"].SetValue(world);

                demoShader.Begin();


                foreach (EffectPass pass in demoShader.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    GraphicsDevice.DrawPrimitives(PrimitiveType.PointList, 0, demoVertices.Length);
                    pass.End();
                }

                demoShader.End();
            }

            VertexDeclaration demoDeclaration;
            VertexBuffer demoVB;
            VertexPositionNormalTexture[] demoVertices;
        }

        uint framesCount = 0;
        uint fps = 0;
        int lastSecond = 0;
        const int CLOUD_COUNT = 5;
        Cloud[] clouds = new Cloud[CLOUD_COUNT];
        Effect demoShader;
        const string APP_TITLE = "Cloud Demo";
        
        Texture2D maskProfileTexture;
        Texture2D noiseTextureClose;
        float noisePhase;

        public Demo()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
            Window.Title = APP_TITLE;
            Camera.Initalize(new Vector3(0, 0, -1), Vector3.Forward);
        }


        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            demofont = Content.Load<SpriteFont>("Font");
            demoShader = Content.Load<Effect>("Cloud");
            maskProfileTexture = Content.Load<Texture2D>("MaskProfile");

            noiseTextureClose = new Texture2D(GraphicsDevice, 128, 128);


            for (uint i = 0; i < CLOUD_COUNT; i++)
            {
                clouds[i] = new Cloud();
                clouds[i].Create(GraphicsDevice, Content);
            }
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

  


            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            Camera.Update();

            noiseTextureClose.Dispose();
            noiseTextureClose = new Texture2D(GraphicsDevice, 128, 128);

            //PerlinNoise.Generate(ref noiseTextureClose, 1, noisePhase);
            CellProcedural.Generate(ref noiseTextureClose, noisePhase);
            noisePhase += 0.004f;

            if (gameTime.TotalRealTime.Seconds > lastSecond)
            {
                lastSecond = gameTime.TotalRealTime.Seconds;
                fps = framesCount;
                framesCount = 0;
            }
            else
            {
                framesCount++;
            }

            GraphicsDevice.Clear(new Color(105, 125, 130));

       
            GraphicsDevice.RenderState.PointSpriteEnable = true;
            GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
            GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            
            GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
            GraphicsDevice.Textures[0] = noiseTextureClose;


            GraphicsDevice.SamplerStates[2].AddressU = TextureAddressMode.Wrap;
            GraphicsDevice.SamplerStates[2].AddressV = TextureAddressMode.Wrap;
            GraphicsDevice.Textures[2] = maskProfileTexture;

            for (uint i = 0; i < CLOUD_COUNT; i++)
            {
                clouds[i].Draw(GraphicsDevice, demoShader);
            }

            spriteBatch.Begin();
            spriteBatch.DrawString(demofont, APP_TITLE + " (3D Perlin noise on CPU)", Vector2.One * 10, Color.Black);
            spriteBatch.DrawString(demofont, "Hardcoded Octaves (Shader)", new Vector2(10, 35), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "Noise phase: " + (Math.Round(noisePhase - (int)noisePhase, 2)), new Vector2(10, 45), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "FPS: " + fps, new Vector2(10, 55), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "Dacre Denny 21/06/09", new Vector2(10, 65), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "www.dacredenny.com", new Vector2(10, 75), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.Draw(noiseTextureClose, new Vector2(20, 100), Color.White);
            spriteBatch.End();
         
            base.Draw(gameTime);
        }
    }
}
